package jmine.tec.web.servlet.taglib.support;

import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.PageContext;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.runtime.RuntimeInstance;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.support.RequestContext;

/**
 * Class to support Rendering of velocity templates from a taglib.
 * 
 * @author takeshi
 */
public final class VelocityTagLibSupport implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private transient RuntimeInstance runtime;

    private transient Context currentContext;

    private transient WebApplicationContext applicationContext;

    private final HttpServletRequest request;

    private final PageContext pageContext;

    /**
     * @param pageContext {@link PageContext}
     */
    public VelocityTagLibSupport(final PageContext pageContext) {
        this.request = (HttpServletRequest) pageContext.getRequest();
        this.pageContext = pageContext;
    }

    /**
     * Gets this tag's current {@link HttpServletRequest} object
     * 
     * @return {@link HttpServletRequest} object
     */
    public HttpServletRequest getRequest() {
        // return (HttpServletRequest) this.pageContext.getRequest();
        return this.request;
    }

    /**
     * @return the runtime
     */
    public RuntimeInstance getRuntime() {
        if (this.runtime == null) {
            this.runtime = this.findBeanOfType(RuntimeInstance.class);
            if (this.runtime == null) {
                this.runtime = new RuntimeInstance();
            }
        }
        return this.runtime;
    }

    /**
     * @return the defaultContext
     */
    public Context getCurrentContext() {
        if (this.currentContext == null) {
            this.currentContext = this.findBeanOfType(Context.class);
            if (this.currentContext == null) {
                this.currentContext = new VelocityContext();
            }
        }
        return this.currentContext;
    }

    /**
     * @param <E> type
     * @param type {@link Class}
     * @return E
     */
    @SuppressWarnings("unchecked")
    public <E> E findBeanOfType(final Class<E> type) {
        if (this.getApplicationContext() == null) {
            return null;
        }
        String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.getApplicationContext(), type);
        if (names.length == 0) {
            return null;
        }
        return (E) this.getApplicationContext().getBean(names[0]);
    }

    /**
     * @return {@link WebApplicationContext}
     */
    public WebApplicationContext getApplicationContext() {
        if (this.applicationContext == null) {
            this.applicationContext =
                    WebApplicationContextUtils.getRequiredWebApplicationContext(this.getRequest().getSession().getServletContext());
        }
        return this.applicationContext;
    }

    /**
     * @param templateName {@link String}
     * @return {@link Template}
     * @throws Exception generic exception
     */
    public Template findTemplate(final String templateName) throws Exception {
        return this.getRuntime().getTemplate(templateName);
    }

    /**
     * Merges the template into the JSP
     * 
     * @param templateName {@link String}
     * @param context {@link Context}
     * @param writer {@link Writer}
     * @throws IOException io exception
     * @throws Exception e
     */
    public void renderTemplate(final String templateName, final Context context, final Writer writer) throws IOException, Exception {
        Template tmp = this.findTemplate(templateName);
        try {
            tmp.merge(context, writer);
        } catch (MethodInvocationException e) {
            throw (Exception) e.getCause();
        }
    }

    /**
     * @param templateName {@link String}
     * @throws IOException IOError
     * @param writer {@link Writer}
     * @throws Exception generic exception
     */
    public void renderTemplate(final String templateName, final Writer writer) throws IOException, Exception {
        this.renderTemplate(templateName, this.newContext(), writer);
    }

    /**
     * Creates a new {@link Context}, using the default context as a parent.
     * 
     * @return {@link Context}
     */
    public Context newContext() {
        VelocityContext ctx = new VelocityContext(this.getCurrentContext());
        ctx.put("pageContext", this.pageContext);
        ctx.put("request", this.getRequest());
        ctx.put("contextPath", this.getRequest().getContextPath());
        ctx.put("messageResolver", new RequestContext(this.getRequest(), this.getRequest().getSession().getServletContext()));
        return ctx;
    }

}
